require 'rubygems'
require 'parse_tree'
require 'ruby2ruby'
require 'sequel'
require 'dataset_expressions'

class Sequel::Dataset
  include Sequel::Dataset::Sequelizer
  
  def filter(*args, &block)
    if block
      filter(block.to_sql(self))
    else
      super(*args)
    end
  end
end

require 'sequel/postgres'
DB = Sequel("postgres://postgres:postgres@localhost:5432/reality_development")
DS = DB[:items]

class Proc
  def to_sql
    DS.translate_expr(self)
  end
end

context "Proc#to_sql" do
  specify "should support <sym> <op> <lit>" do
    proc {:x > 100}.to_sql.should == 'x > 100'
    proc {:x < 100}.to_sql.should == 'x < 100'
    proc {:x >= 100}.to_sql.should == 'x >= 100'
    proc {:x <= 100}.to_sql.should == 'x <= 100'
    proc {:x == 100}.to_sql.should == 'x = 100'
  end
  
  specify "should support number literals" do
    proc {:x > 123.45}.to_sql.should == 'x > 123.45'
    proc {:x > -30_000}.to_sql.should == 'x > -30000'
  end

  specify "should support string literals" do
    proc {:x == 'abc'}.to_sql.should == "x = 'abc'"
    proc {:y == "ab'cd"}.to_sql.should == "y = 'ab''cd'"
  end
  
  specify "should support boolean literals" do
    proc {:x == false}.to_sql.should == "x = 'f'"
    proc {:x == true}.to_sql.should == "x = 't'"
  end
  
  specify "should support nil literal and nil?" do
    proc {:x == nil}.to_sql.should == "x IS NULL"
    proc {:x.nil?}.to_sql.should == "x IS NULL"
  end
  
  specify "should support local vars or method references" do
    proc {proc {:x == a}.to_sql}.should raise_error(NameError)
    b = 123
    proc {:x == b}.to_sql.should == "x = 123"
    def xyz; 321; end
    proc {:x == xyz}.to_sql.should == "x = 321"
    proc {:x == xyz.to_s}.to_sql.should == "x = '321'"
  end
  
  specify "should support constants" do
    ZZZ = 444
    proc {:x == ZZZ}.to_sql.should == "x = 444"
    
    CCC = Module.new
    CCC::DDD = 'hi'
    proc {:x == CCC::DDD}.to_sql.should == "x = 'hi'"
  end
  
  specify "should support instance attributes" do
    @abc = 123
    proc {:x == @abc}.to_sql.should == "x = 123"
  end
  
  specify "should support class attributes" do
    @@abc = 321
    proc {:x == @@abc}.to_sql.should == "x = 321"
  end
  
  specify "should support like? pattern" do
    proc {:x.like? '%abc'}.to_sql.should == "x LIKE '%abc'"
  end
  
  specify "should support =~ operator" do
    # stock SQL version does not know about regexps
    proc {:x =~ '123'}.to_sql.should == "x = '123'"
  end
  
  specify "should support != operator" do
    proc {:x != 100}.to_sql.should == "NOT (x = 100)"
  end

  specify "should support !~ operator" do
    proc {:x !~ '123'}.to_sql.should == "NOT (x = '123')"
  end
  
  specify "should support ! operator" do
    proc {!:x}.to_sql.should == "x = 'f'"
    proc {!(:x > 100)}.to_sql.should == "NOT (x > 100)"
  end
  
  specify "should support && operator" do
    proc {1 && 2}.to_sql.should == "(1) AND (2)"
    proc {:x > 100 && :y < 100}.to_sql.should == "(x > 100) AND (y < 100)"
    proc {:x && :y && :z}.to_sql.should == "(x) AND ((y) AND (z))"
  end
  
  specify "should concatenate separate statements using AND" do
    proc {:x == 20; :y == 30}.to_sql.should == "(x = 20) AND (y = 30)"
    proc do
      :x != 1
      :y != 2
      :z != 3
    end.to_sql.should == "(NOT (x = 1)) AND (NOT (y = 2)) AND (NOT (z = 3))"
  end
  
  specify "should support || operator" do
    proc {1 || 2}.to_sql.should == "(1) OR (2)"
    proc {:x > 100 || :y < 100}.to_sql.should == "(x > 100) OR (y < 100)"
    proc {:x || :y || :z}.to_sql.should == "(x) OR ((y) OR (z))"
  end
  
  specify "should support operator combinations" do
    proc {(:x > 1 || :y > 2) && (:z > 3)}.to_sql.should == "((x > 1) OR (y > 2)) AND (z > 3)"
    proc {(1 && 2) || (3 || 4)}.to_sql.should == "((1) AND (2)) OR ((3) OR (4))"
    proc {(:x != 2) || (:y == 3) || !(:z == 4)}.to_sql.should == "(NOT (x = 2)) OR ((y = 3) OR (NOT (z = 4)))"
  end
  
  specify "should support late bound column references" do
    def abc; :tttt; end
    proc {abc > 2}.to_sql.should == "tttt > 2"
  end
  
  specify "should support qualified column references" do
    proc {:x__y > 3}.to_sql.should == "x.y > 3"
  end
  
  specify "should support functions on columns" do
    proc {:x.MAX > 100}.to_sql.should == "max(x) > 100"
    proc {:x.COUNT > 100}.to_sql.should == "count(x) > 100"
  end
  
  specify "should do stuff like..." do
    proc {:price < 100 || :category != 'ruby'}.to_sql.should == \
      "(price < 100) OR (NOT (category = 'ruby'))"
    t = Time.now
    proc {:node_id == 1 && :stamp < t}.to_sql.should == \
      "(node_id = 1) AND (stamp < #{DS.literal(t)})"
      
    proc {1 < :x}.to_sql.should == "1 < x"
  end
  
  specify "should complain if someone is crazy" do
    proc {proc {def x; 1; end}.to_sql}.should raise_error(SequelError)
    a = 1
    proc {proc {a = 1}.to_sql}.should raise_error(SequelError)
  end
end
